﻿/*
	Class Description: Vertical ScrollBar GUI built using graphics API
	------------------------------------------------------------------------
	                          APACHE LICENSE
	------------------------------------------------------------------------
	Copyright 2010 Vu Thien Nguyen Phong
	
	Licensed under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at
	
	http://www.apache.org/licenses/LICENSE-2.0
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
*/

package com.minidebugger.gui {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.text.TextField;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;

	public class VScrollBar extends Sprite {
		private const ARROW_BUTTON_COLOR:uint = 0xff000000;
		private const ARROW_BUTTON_SIZE:int = 7;
		private const TRACK_SKIN_COLOR:uint = 0xffcccccc;
		private const THUMB_BORDER_COLOR:uint = 0xffaaaaaa;
		private const THUMB_BG_COLOR:uint = 0xffcccccc;
		private const SMALL_SCROLL_STEP:int = 1; // scroll 1 unit when click on arrows button
		private const BIG_SCROLL_STEP:int = 5; // scroll 5 units when click on scrollTrack
		
		// declare a TextField that linked to the scrollbar
		private var tField:TextField;
		
		// components of the scrollbar
		private var scrollUp_mc:Sprite; // the arrow button to scroll up textfield
		private var scrollDown_mc:Sprite; // the arrow button to scroll down textfield		
		private var scrollTrack_mc:Sprite; // invisible sprite to receive MouseClick event
		private var scrollTrackSkin_mc:Sprite; // visible sprite of scrollTrack_mc
		private var scrollThumb_mc:Sprite; 
		
		public function VScrollBar(tField:TextField) {
			this.tField = tField;
			
			addEventListener(Event.ADDED_TO_STAGE,init);
		}
		
		private function init(e:Event):void{
			removeEventListener(Event.ADDED_TO_STAGE,init);
			
			// entry point
			addUpButton();
			addDownButton();
			addScrollTrackSkin();
			addScrollTrack();
			addScrollThumb();
			
			this.addEventListener(MouseEvent.CLICK,scrollBarClicked)
			tField.addEventListener(Event.SCROLL,updateScrollThumbPosition);
			tField.mouseWheelEnabled = false; // disable mouse wheel event of textField
		}
		
		// draw and add the up arrow button for scrollbar
		private function addUpButton(){
			scrollUp_mc = new Sprite();
			
			scrollUp_mc.graphics.beginFill(ARROW_BUTTON_COLOR);
			scrollUp_mc.graphics.lineTo(ARROW_BUTTON_SIZE,ARROW_BUTTON_SIZE);
			scrollUp_mc.graphics.lineTo(-ARROW_BUTTON_SIZE,ARROW_BUTTON_SIZE);
			scrollUp_mc.graphics.lineTo(0,0);
			scrollUp_mc.graphics.endFill();
			
			this.addChild(scrollUp_mc);
		}
		
		// draw and add the scroll down button
		private function addDownButton(){
			scrollDown_mc = new Sprite();
			
			scrollDown_mc.graphics.beginFill(ARROW_BUTTON_COLOR);
			scrollDown_mc.graphics.lineTo(ARROW_BUTTON_SIZE,0);
			scrollDown_mc.graphics.lineTo(0,ARROW_BUTTON_SIZE);
			scrollDown_mc.graphics.lineTo(-ARROW_BUTTON_SIZE,0);
			scrollDown_mc.graphics.lineTo(0,0);
			scrollDown_mc.graphics.endFill();
			scrollDown_mc.y = tField.height - ARROW_BUTTON_SIZE;
			
			this.addChild(scrollDown_mc);
		}
		
		// draw and add scroll track skin (this is what users will see)
		private function addScrollTrackSkin():void{
			scrollTrackSkin_mc = new Sprite();
			
			scrollTrackSkin_mc.graphics.lineStyle(1,TRACK_SKIN_COLOR);
			scrollTrackSkin_mc.graphics.lineTo(0,scrollDown_mc.y-scrollUp_mc.y-ARROW_BUTTON_SIZE);
			scrollTrackSkin_mc.y = scrollUp_mc.y + ARROW_BUTTON_SIZE;
			scrollTrackSkin_mc.mouseEnabled = false;
			
			this.addChild(scrollTrackSkin_mc);
		}
		
		// draw and add the actual scroll track (this is what we add event listener in code)
		private function addScrollTrack():void{
			scrollTrack_mc = new Sprite();
			
			scrollTrack_mc.graphics.beginFill(0xff000000);
			scrollTrack_mc.graphics.drawRect(0,0,ARROW_BUTTON_SIZE*2,scrollTrackSkin_mc.height);
			scrollTrack_mc.graphics.endFill();
			scrollTrack_mc.x = -ARROW_BUTTON_SIZE;
			scrollTrack_mc.y = scrollTrackSkin_mc.y;
			scrollTrack_mc.alpha = 0;
			
			this.addChild(scrollTrack_mc);
		}
		
		// draw and add the scroll thumb
		private function addScrollThumb():void{
			scrollThumb_mc = new Sprite();
			
			scrollThumb_mc.graphics.lineStyle(1,THUMB_BORDER_COLOR);
			scrollThumb_mc.graphics.beginFill(THUMB_BG_COLOR);
			scrollThumb_mc.graphics.drawRect(0,0,ARROW_BUTTON_SIZE*2,ARROW_BUTTON_SIZE);
			scrollThumb_mc.graphics.endFill();
			scrollThumb_mc.x = scrollTrackSkin_mc.x - ARROW_BUTTON_SIZE;
			scrollThumb_mc.y = scrollTrackSkin_mc.y;
			
			scrollThumb_mc.addEventListener(MouseEvent.MOUSE_DOWN,startDragScrollThumb);
			scrollThumb_mc.addEventListener(MouseEvent.MOUSE_UP,stopDragScrollThumb);
			
			this.addChild(scrollThumb_mc);
		}
		
		private function startDragScrollThumb(e:Event):void{
			scrollThumb_mc.startDrag(true,new Rectangle(-scrollThumb_mc.width/2,ARROW_BUTTON_SIZE,0,scrollTrackSkin_mc.height-ARROW_BUTTON_SIZE));
			addEventListener(Event.ENTER_FRAME,movingScrollThumb);
			stage.addEventListener(MouseEvent.MOUSE_UP,stopDragScrollThumb);
		}
		
		private function stopDragScrollThumb(e:Event):void{
			scrollThumb_mc.stopDrag();
			stage.removeEventListener(MouseEvent.MOUSE_UP,stopDragScrollThumb);
			removeEventListener(Event.ENTER_FRAME,movingScrollThumb);
		}
		
		private function movingScrollThumb(e:Event):void{
			var scrollSpaceRemain:Number = scrollTrackSkin_mc.height - scrollThumb_mc.y - scrollThumb_mc.height + ARROW_BUTTON_SIZE;
			var numLinesHidden:int = int(scrollSpaceRemain * (tField.maxScrollV - 1) / (scrollTrackSkin_mc.height - scrollThumb_mc.height));
			
			tField.scrollV = tField.maxScrollV - numLinesHidden;
		}
		
		// update position of the scrollThumb according to content in textfield, the most
		// important function in this class. The logic of this function is very simple:
		//		+ when there is no scroll in the textfield (the remaining lines can be scroll =
		// maxScrollV-scrollV), the scrollThumb will be positioned at the top (remaining space can
		// be scrolled = scrollTrack.height - scrollThumb.height).
		//		+ if text in the textfield is scroll (scrollV changed), we know the new number of
		// remaining lines can be scrolled. Based on that we can calculate the remaining space which
		// scrollbar can scroll down; and eventually, the new y-position of scrollThumb.
		public function updateScrollThumbPosition(e:Event=null):void{
			// the remaining lines at the bottom of textfield which didn't scroll yet
			var numLinesHidden:int = tField.maxScrollV - tField.scrollV;
			// the space that scrollbar still can scroll down
			var scrollSpaceRemain:Number = numLinesHidden * (scrollTrackSkin_mc.height - scrollThumb_mc.height) / (tField.maxScrollV-1);
			scrollThumb_mc.y = scrollTrackSkin_mc.height - (scrollThumb_mc.height + scrollSpaceRemain) + ARROW_BUTTON_SIZE;
			
			if (scrollThumb_mc.y < scrollTrackSkin_mc.y)
				scrollThumb_mc.y = scrollTrackSkin_mc.y;
		}
		
		private function scrollBarClicked(e:MouseEvent):void{
			if (e.target==scrollUp_mc){ // if users click on the scroll up button
				if (tField.scrollV>1){
					if (tField.scrollV-SMALL_SCROLL_STEP<=1)
						tField.scrollV = 1;
					else
						tField.scrollV -= SMALL_SCROLL_STEP;
					updateScrollThumbPosition();
				}
			}
			else if (e.target==scrollDown_mc){ // if click on scroll down button
				if (tField.maxScrollV>tField.scrollV&&tField.scrollV>0){
					if (tField.scrollV+SMALL_SCROLL_STEP>=tField.maxScrollV)
						tField.scrollV = tField.maxScrollV;
					else
						tField.scrollV += SMALL_SCROLL_STEP;
					updateScrollThumbPosition();
				}
			}
			else if (e.target==scrollTrack_mc){ // if users click on scrollTrack
				if (e.localY > scrollThumb_mc.y+scrollThumb_mc.height){ // if click point is below scrollThumb -> scroll down
					if (tField.maxScrollV>tField.scrollV&&tField.scrollV>0)
					{
						if (tField.scrollV+BIG_SCROLL_STEP>=tField.maxScrollV)
							tField.scrollV = tField.maxScrollV;
						else
							tField.scrollV += BIG_SCROLL_STEP;
						updateScrollThumbPosition();	
					}
				}
				else{ // scroll up
					if (tField.scrollV>1){
						if (tField.scrollV-BIG_SCROLL_STEP<=1)
							tField.scrollV = 1;
						else
							tField.scrollV -= BIG_SCROLL_STEP;
					}
					updateScrollThumbPosition();
				}
			}
			
		}
	}
}
